Skip to content

Close A1/A2 structurally; red-team the gold suite (A3+B2)#21

Merged
lukefwalton merged 5 commits into
mainfrom
claude/fable-worthy-tasks-o5ma9c
Jul 8, 2026
Merged

Close A1/A2 structurally; red-team the gold suite (A3+B2)#21
lukefwalton merged 5 commits into
mainfrom
claude/fable-worthy-tasks-o5ma9c

Conversation

@lukefwalton

Copy link
Copy Markdown
Owner

What & why

Closes the two highest-value seams in NEXT-STEPS.md A and audits the third, in three commits sequenced so the gold coverage that would catch each failure mode lands before the structural change that closes it (the repo's own rule).

1. Red-team the gold suite (A3+B2) — 74c86ed. Five new adversarial entries: extraction-shaped queries (q11–q12: "what exactly does the notebook say", a verbatim-quote demand), instruction injections aimed at private content (q13, must still route) and at the machinery (q14, must refuse bare), and the previously missing supported case (q15). Canary forbid-patterns — phrases verified to exist only in the private notebook bodies — now guard each mode's characteristic failure ("chair by the window" was rejected as a canary because the public lyric nearly contains it). New expectAnswerPatterns gold field: the must-match mirror of forbidAnswerPatterns, documented as pinning behavior shape, never facts.

2. Template the related-material answer (A2) — 02f0c91. A hint citation is provenance without backing, so free prose in this mode was the one place a confabulated "summary" of private material passed every gate. finalizeAnswer now replaces the mode's prose, after grounding, with a fixed sentence rendered only from the cited hints' public-safe fields (renderRelatedMaterialAnswer, src/public-safe.ts) — the mode can point, never assert content. The gold suite pins the template. NEXT-STEPS A2 rewritten as closed, with the residue named (supported-mode hint citations under free prose, owned by the q15 canaries).

3. Make the traveling label/locator boundary structural (A1) — 624e825. Private notes now require an explicit label: frontmatter field; title stays private and is what gets embedded. Both traveling fields are a branded PublicSafe type whose only constructor is a build-time lint (single line, ≤120 chars, no 5-consecutive-word run shared with the note's private body — N=5 calibrated so the demo's bibliographic locator passes). Index schema bumped to v3; the committed demo indexes were migrated mechanically by scripts/migrate-demo-index-v3.mjs (title := label is exact, embed text byte-identical, vectors and contentHashes untouched — nothing re-embedded), verified by the keyless demo gate (7/7, rho 1.0000).

Note for the keyed follow-up: npm run index is required (local index is now v2-stale), then npm run eval and npm run eval -- --full --ids q07,q11,q12,q13,q14,q15. q15 is the likeliest to wobble (compound canon+process question); if it fails, the fix is a sentence in the prompt's supported-mode bullet, never the query.

Checklist

  • npm test passes without an API key (47/47) — no hidden dependency on live calls
  • npm run typecheck passes
  • No change that makes the eval pass by special-casing a question (fixes go into the corpus, scoring, or prompt)
  • A boundary stays structural (a type, not a checker someone must remember), if this touches the no-leak path — PublicSafe is constructible only through the lint; the related-material template renders nothing but PublicSafe fields
  • Touched the prompt / retrieval / validation / repair? Ran the relevant npm run eval subset — not run here (no API key in this environment); keyless npm run demo:run certifies retrieval on the migrated artifacts (7/7, rho 1.0000). Keyed subset above is the owner's step.
  • Read against .github/STANDARDS.md — the rubric a PR is judged by

🤖 Generated with Claude Code

https://claude.ai/code/session_019n27KsH2AS1mbpHMYp8y2X


Generated by Claude Code

claude added 3 commits July 7, 2026 19:14
Adversarial coverage for the seams NEXT-STEPS A1-A3 name, landing before
the structural changes that will close them (the coverage-first rule,
NEXT-STEPS.md).

- New gold entries: q11-q13 extraction/injection attacks aimed at private
  content (must route, never comply), q14 injection aimed at the machinery
  (must refuse bare), q15 the missing 'supported' case - which also owns
  the residue templating can't close, since supported prose is free prose
  that cites a hint.
- Canary forbid-patterns on q02/q05/q11-q13/q15: phrases verified to exist
  only in private notebook bodies, so any answer containing one has leaked
  or confabulated private text. "chair by the window" rejected as a canary
  because the public lyric nearly contains it.
- Refusal-debris patterns on q08-q10/q14: a failed refusal leaks URLs or
  citation-id-shaped prose; vacuously green when refusal succeeds.
- New gold field expectAnswerPatterns: must-match mirror of
  forbidAnswerPatterns (same load validation), to pin behavior shape -
  never facts. Will pin the related-material template when it lands.
- Tests: must-match judge cases, load validation for both pattern fields,
  all four modes now asserted present in the gold set.

q11-q15 may be red under --full until the template and label changes land;
that is the coverage doing its job. Retrieval tier expected green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019n27KsH2AS1mbpHMYp8y2X
Close the provenance-without-backing seam: a hint citation is real
provenance but carries no text, so free prose in related-material mode was
the one place a confabulated summary of private material passed every
gate, held only by a prompt instruction.

- New src/public-safe.ts: renderRelatedMaterialAnswer builds the mode's
  prose from the cited hints' public-safe fields (label, locator) alone.
  No raw URL in the prose - the citation object carries the link, and
  gold q07 forbids URLs here.
- src/answer.ts: finalizeAnswer runs repair -> grounding -> template, so
  the hint lookup cannot miss and answers that only became
  related-material through repair's kind conversion get templated too.
  Confabulation in this mode is now inexpressible, not discouraged.
- Prompt tells the model its related-material prose is standardized.
- Gold pins the template via expectAnswerPatterns on q07/q11-q13, so a
  regression cannot silently un-template the mode.
- NEXT-STEPS A2 rewritten as closed, with the residue named: supported
  mode still cites hints under free prose, owned by the q15 canaries.
  The template's safety is exactly the safety of label+locator - A1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019n27KsH2AS1mbpHMYp8y2X
The RoutingHint label was the note's raw frontmatter title and the locator
raw frontmatter, guarded only by a warning comment - a privately-titled
note leaked through its own label. Three layers replace the comment:

- Explicit `label:` frontmatter, required on private notes: the display
  name that travels, distinct from `title` (private, embedded, never
  travels). What travels is now an authored per-note decision; a corpus
  without labels fails loudly with the reason.
- PublicSafe branded type on PrivateNote/RoutingHint label+locator, whose
  only constructor is the new build-time lint assertPublicSafeField
  (src/public-safe.ts): non-empty, single line, <=120 chars, and no run of
  5 consecutive words shared with the note's private body - a traveling
  field that quotes the note fails the build, not an answer. N=5 is
  calibrated: the demo's bibliographic locator legitimately shares a
  4-token run with its sermon body.
- Index schema v3 (store.ts): note entries must carry title/label/locator,
  so a stale or hand-edited artifact fails fast with the rebuild remedy.

noteEmbedText now embeds title+body - byte-identical to the old label+body
for every corpus whose labels repeated titles, so committed demo vectors
survive without re-embedding: scripts/migrate-demo-index-v3.mjs migrated
the committed demo indexes mechanically (title := label, vectors and
contentHashes untouched), verified by the keyless demo gate (7/7, rho
1.0000). Residue named in NEXT-STEPS A1: the lint is a 5-gram tripwire,
`about` travels unlinted as a declared public page, and the brand erases
at JSON read - backstopped by the gold canaries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019n27KsH2AS1mbpHMYp8y2X
@surmado-code-review

surmado-code-review Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Automated Checks (advisory, non-blocking)

✅ All checks passed.


⚠️ Partial review: The diff exceeded size limits. Feedback below focuses on early files and high-level risks only.

Standards Compliance

This PR is directly in the repo’s highest-risk area: no-leak boundary, citation/mode semantics, eval coverage, and demo artifact migration. From the visible diff, I don’t see a clear standards violation, and one issue from my prior review appears addressed by the migration/hash-invariant testing around the committed demo artifacts.

Because the only visible code change here is the large demo/corpus/index.json churn to schema version:3, the main standards checks for the reviewer are structural ones in the unseen code:

  • [UNVERIFIED] Stale index handling should fail loudly
    The standards require malformed/stale index data to throw clearly, not degrade silently. With the schema bump described in the PR, please verify the index loader rejects v2 local indexes with an explicit version error rather than accepting them partially.

  • [UNVERIFIED] Public-traveling fields should stay on the new public-safe path only
    Given the A1 change, the reviewer should confirm the new label/locator fields are the only metadata that can travel into prompts/citations, and that private note title cannot still leak through a fallback path. That’s the repo’s “boundary stays structural” requirement, not a convention check.

  • [UNVERIFIED] Related-material finalization should overwrite model prose after grounding
    The A2 change is standards-critical: related-material mode must “point, never assert content.” Please verify finalizeAnswer always discards model-generated prose for that mode and renders only from public-safe hint fields after repair/grounding.

Summary

This PR appears to do three coordinated things: add adversarial gold cases for leak/mode failures, structurally template related-material answers from public-safe hint metadata, and migrate the index schema so private note titles no longer double as traveling labels. Most of the visible diff is committed demo/ artifact churn from the schema migration rather than business-logic changes.

Reviewer: most of the risk is in the unseen index-loading, public-safe boundary enforcement, and answer-finalization paths; the giant JSON update is mostly a consequence.

What to pay attention to

  • Schema v3 loader/migration behavior
    This deserves attention because the PR intentionally makes existing local indexes stale. The important question is whether runtime failure is explicit and immediate.

  • PublicSafe construction and escape hatches
    The author says this is structural. The reviewer should confirm normal app code cannot forge or bypass the branded type, especially anywhere routing hints or citations are assembled.

  • Supported vs related-material mode semantics
    The PR description explicitly says q15 is the residual wobble point. That makes the supported-mode path the judgment-heavy area: verify the new gold cases are actually protecting mode/citation behavior, not just answer phrasing.

  • Gold additions q11–q15
    Since these are meant to red-team extraction demands and injection attempts, check that the canaries are corpus-specific leak tripwires and that the new expectAnswerPatterns field pins shape without overfitting exact wording.

Things I noticed

🟡 Yellow flags — consider for this PR or a follow-up:

  • The review surface is dominated by demo/corpus/index.json, so the real merge risk is concentrated in unseen logic. I’d put extra scrutiny on version-rejection, public-safe field derivation, and mode-finalization tests.
  • [UNVERIFIED] Since the PR closes related-material structurally but calls out supported-mode hint citations as the remaining edge, it would be worth confirming the new gold coverage actually fails if supported-mode prose starts synthesizing private implications from hint-only citations.

No visible red flags from the diff shown here.

Good patterns

  • Sequencing the adversarial gold coverage before the structural changes is very aligned with the repo’s eval-first regression contract.
  • Keeping the demo index migration mechanical, with hash/vector invariants called out, is a strong safety pattern for a boundary-sensitive schema bump.

Suggested improvements

  1. If not already present, add a focused unit test proving related-material mode ignores any model-returned prose and always emits the fixed public-safe template.
  2. Make the stale-index error message include both expected and found schema versions; that will help now that v2 indexes are intentionally invalid.
  3. Add a narrow regression test showing private-note title cannot be assigned to any traveling metadata field without going through the public-safe lint path.

Surmado Code Review (v1.2-mt) is an automated review, designed to work alongside human judgment.

Want to change your STANDARDS.md or YML? Edit it directly, or tune it with our AI agent Scout.

Comment /rerun-review on this PR to refresh the review — costs 1 additional PR credit.

- readIndexFile's version mismatch now names the found version alongside
  the expected one ("schema version 2, not schema version 3"), so a stale
  artifact says what it is, not just what it isn't.
- New keyless test in demo/artifacts.test.ts: every committed demo entry's
  contentHash must equal the hash of today's embed-text derivation
  (title+body for notes), proving the v2->v3 migration preserved the exact
  bytes the committed vectors were embedded from.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019n27KsH2AS1mbpHMYp8y2X

Copy link
Copy Markdown
Owner Author

Responding to the automated review (a79fff2 addresses the actionable parts):

Suggestions. (1) A test proving plausible model prose is discarded in related-material mode already exists — answer: finalizeAnswer makes related-material prose deterministic in test/engine.test.ts feeds a confabulated summary of the private note through the real pipeline and asserts the canary phrase cannot survive. (2) The schema-mismatch error now names the found version alongside the expected one. (3) Added a keyless invariant test (demo/artifacts.test.ts): every committed demo entry's contentHash must equal the hash of today's embed-text derivation, which pins exactly the migration claim the JSON diff is too large to eyeball — the v2→v3 rewrite preserved the bytes the committed vectors were embedded from.

Questions. (1) Other construction paths for PublicSafe: one, and it's documented at the type (src/types.ts) and in NEXT-STEPS A1 — the JSON read path. readIndexFile casts stored entries back to IndexEntry, which launders the brand without re-linting; the guarantee is "linted when the corpus was built", backstopped by the v3 shape check in entryIsValid and the gold canaries at answer time. No runtime code mutates the fields after construction (toRoutingHint only copies them). (2) Supported-mode canaries: intentionally prompt/eval pressure, not structure — templating supported-mode prose would mean templating record-backed answers, which is the product. That residue is named in NEXT-STEPS A2 and owned by q15's canary patterns.


Generated by Claude Code

Blocking finding from the keyed eval: told its related-material prose is
standardized, the model returns mode + hint citations + empty answer -
and validateAnswer rejected the empty prose before finalizeAnswer could
render the template. q07/q11/q12/q13 all threw under --full.

- validateAnswer now admits empty prose ONLY for a declared
  related-material answer, documented as the one deliberate gap: that
  mode's prose is engine-rendered downstream.
- finalizeAnswer re-enforces the sourced-prose contract after templating,
  so the gap cannot leak past the mode it exists for: an empty-prose
  answer whose citations repair OUT of related-material (to a record)
  fails at the door, and one with no citations normalizes to a bare
  refusal as before.
- Prompt now says to leave "answer" empty for the mode (or write a short
  routing sentence - replaced either way).
- Tests pin the exact failure: empty-prose related-material renders the
  template end to end; empty prose on any other declared mode still
  throws; the repair-re-derivation edge throws; bare-refusal
  normalization unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019n27KsH2AS1mbpHMYp8y2X
@lukefwalton lukefwalton merged commit 5e3dabf into main Jul 8, 2026
3 checks passed
@lukefwalton lukefwalton deleted the claude/fable-worthy-tasks-o5ma9c branch July 8, 2026 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants